package com.xuliugen.javamybatis.demo.proxy.dynamicproxy;

import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * Created by xuliugen on 2017/10/7.
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    //真正的业务对象，也就是RealSubject对象
    private Class<T> mapperInterface;

    /**
     * 构造函数
     */
    public MapperProxy(Class<T> target) {
        this.mapperInterface = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        /*
         * 调用代理$Proxy0的toString方法，实际上调用的是invovationHandler，h中的toString方法，方法体为:
         * return (String)super.h.invoke(this, m2, (Object[])null);
         * 因此，我们要么重写Proxy代理对象的toString方法，要么在h.invoke方法中指定调用Object的toString方法！
         * 所以就会出现Method threw 'java.lang.ClassCastException' exception. Cannot evaluate com.sun.proxy.$Proxy0.toString()无法评估的问题！
         */
        try {
            //如果目标方法继承自Object，则直接调用目标方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }

        //1、在执行业务方法之前进行预处理
        //System.out.println("------执行之前------");
        //2、调用具体方法
        //Object result = method.invoke(target, args);

        //Object result = "移交给其他类实现！";

        //3、在执行业务方法之后进行后置处理
        //System.out.println("------执行之后------");

        final MapperMethod mapperMethod = new MapperMethod();
        return mapperMethod.execute(args);
    }

    private boolean isDefaultMethod(Method method) {
        return ((method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
                && method.getDeclaringClass().isInterface();
    }

    @UsesJava7
    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }
}



